$MOD186
$EP
Name AudioSynthesizer

;---------------------------------------------------------------------------
; Audio Synthesizer for 8088 microcomputer system
;
; Filename:	AudioSynthesizer.ASM
;
; Author: 	Phang Tian He
;
; This file contains proprietory information and cannot be copied 
; or distributed without prior permission from the author.
;---------------------------------------------------------------------------
;API:
public Audio_init				; Called at the beginning of the program to ensure the Audio Sythesizer is initialized probably.
public Play_This_Sound			; Play a sound according to the given index.
public OutputNextLevel			; Called by the timer interrupt(or desired timing control unit) to output the next voltage level to the DAC
public Audio_Change_Playlist	; Play a whole series of sound index by a series of given index.
public FetchNextIndex 			; Must call this function periodically, or else, the play list function will not work. Frequency can be much slower.
public StopSound				; Stop the sound immediately

LOCAL_INDEX_BUFFER_SIZE	EQU	64
TRUE					EQU 1
FALSE 					EQU 0

SoundVariebles	SEGMENT
	Current_ES		DW	0000H	; Current Sound ES location
	Current_BP		DW	0000H	; Current Sound BP location
	Current_Count	DW	0000H	; Current Sound Counter
	
	; Create the lookup table locations.
	; 27 Sounds, ES, BP, Count for each of them-> 27*3=81
	;					   |  ES  |  BP   | Count |
	Lookup_Index_0		DW	4000H,	0000H,	0320H	; Index:0	Sound:Nil
	Lookup_Index_1		DW	4000H,	0320H,	1004H	; Index:1	Sound:Zero
	Lookup_Index_2		DW	4000H,	1324H,	0C2CH	; Index:2	Sound:One
	Lookup_Index_3		DW	4000H,	1F50H,	0A63H	; Index:3	Sound:Two
	Lookup_Index_4		DW	4000H,	29B3H,	0A89H	; Index:4	Sound:Three
	Lookup_Index_5		DW	4000H,	343CH,	08FAH	; Index:5	Sound:Four
	Lookup_Index_6		DW	4000H,	3D36H,	0B7AH	; Index:6	Sound:Five
	Lookup_Index_7		DW	4000H,	48B0H,	03D9H	; Index:7	Sound:Six
	Lookup_Index_8		DW	4000H,	4C89H,	0A06H	; Index:8	Sound:Seven
	Lookup_Index_9		DW	4000H,	568FH,	060DH	; Index:9	Sound:Eight
	Lookup_Index_10		DW	4000H,	5C9CH,	0EF3H	; Index:10	Sound:Nine
	Lookup_Index_11		DW	4000H,	6B8FH,	0986H	; Index:11	Sound:Ten
	Lookup_Index_12		DW	4000H,	7515H,	0E8CH	; Index:12	Sound:Eleven
	Lookup_Index_13		DW	4000H,	83A1H,	0AADH	; Index:13	Sound:Twelve
	Lookup_Index_14		DW	4000H,	8E4EH,	05AEH	; Index:14	Sound:Third
	Lookup_Index_15		DW	4000H,	93FCH,	091CH	; Index:15	Sound:Forth
	Lookup_Index_16		DW	4000H,	9D18H,	03FEH	; Index:16	Sound:Fifth
	Lookup_Index_17		DW	4A00H,	0116H,	03E9H	; Index:17	Sound:Sixth
	Lookup_Index_18		DW	4A00H,	04FFH,	0870H	; Index:18	Sound:Seventh
	Lookup_Index_19		DW	4A00H,	0D6FH,	060DH	; Index:19	Sound:Eighth
	Lookup_Index_20		DW	4A00H,	137CH,	0E19H	; Index:20	Sound:Nineth
	Lookup_Index_21		DW	4A00H,	2195H,	06E1H	; Index:21	Sound:Teen
	Lookup_Index_22		DW	4A00H,	2876H,	05EBH	; Index:22	Sound:Ty
	Lookup_Index_23		DW	4A00H,	2E61H,	0830H	; Index:23	Sound:Twenty
	Lookup_Index_24		DW	4A00H,	3691H,	08EFH	; Index:24	Sound:Hundred
	Lookup_Index_25		DW	4A00H,	3F80H,	09ACH	; Index:25	Sound:Thousand
	Lookup_Index_26		DW	4A00H,	492CH,	0CF7H	; Index:26	Sound:Million
	Lookup_Index_27		DW	4A00H,	5623H,	0DC3H	; Index:27	Sound:Dollars
	Lookup_Index_28		DW	4A00H,	63E6H,	0479H	; Index:28	Sound:Cents

	INDEX_BUFFER 		DB LOCAL_INDEX_BUFFER_SIZE DUP(0)
	STORED_INDEX		DB 0
	INDEX_PTR			DB 0
	PLAY_INDEX_LIST		DB FALSE

SoundVariebles	ENDS

LOOKUP_BASE						EQU	OFFSET Lookup_Index_0  	;defined in the orignal code
INDEX_BASE						EQU OFFSET INDEX_BUFFER 	;defined of Index Base
SOUND_SYNTHESIZER_IO_ADDRESS	EQU	0200H



Audio_Routine 	SEGMENT
ASSUME CS:Audio_Routine

;=====================================================================
;	FUNCTION NAME: Audio_init
;	INPUTS: 		-
;	OUTPUTS:		-
;	DESCRIPTION:	INITIALIZATION CODE FOR AUDIO SYNTHESIZER.
;	WHEN TO CALL:	ONCE AFTER SYSTEM WAS STARTED.
;=====================================================================
Audio_init PROC FAR
	RET
Audio_init ENDP


;=====================================================================
;	FUNCTION NAME: Play_This_Sound
;	INPUTS: 		AL-> INDEX OF THE SOUND @ LookUpTable
;	OUTPUTS:		-
;	DESCRIPTION:	CHANGE PLAYING SOUND ACCORDING TO THE GIVEN INDEX
;					STORED IN AX REGISTER REGARDLESS OF WHETHER THE 
;					SYSTEM IS CURRENTLY PLAYYING SOUND OR NOT.
;	WHEN TO CALL:	WHEN THE SYSTEM WANT TO CHANGE/START THE SOUND.
;=====================================================================
Play_This_Sound PROC FAR
	PUSH 	BX
	PUSH 	ES

	; INDICATING THAT THE CURRENT SOUND IS NOT FROM THE INDEX LIST
	MOV 	BX, SoundVariebles
	MOV 	ES, BX
	MOV 	BYTE PTR ES:PLAY_INDEX_LIST,  FALSE

	; CHANGE THE SOUND AS REQUEST
	CALL 	FAR PTR ChangeNewSound

	POP 	ES
	POP 	BX
	RET
Play_This_Sound ENDP

;=====================================================================
;	FUNCTION NAME: 	Audio_Change_Playlist
;	INPUTS: 		AX -> The segment address of the source buffer.
;					BX -> The offset address of the source buffer.
;					CL -> The total number of index to be played.
;	OUTPUTS:		-
;	DESCRIPTION:	LET THE AUDIO SYNTHESIZER PROGRAM TO AUTOMATICALLY
;					TO PLAY A LIST OF SOUND INDEX
;	WHEN TO CALL:	WHEN THE SYSTEM WANT TO PLAY A NEW LIST OF SOUND.
;=====================================================================
Audio_Change_Playlist PROC FAR
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH 	DI
	PUSH 	SI
	PUSH	ES
	PUSH 	DS

	; CHECK THE 'CL' IS VALID, IF BIGGER THAN BUFFER SIZE, QUIT!!!!
	CMP 	CL, LOCAL_INDEX_BUFFER_SIZE
	JG 		CHANGE_PLAYLIST_QUITING

	; IF 'CL' IS 0, QUIT!!!! NO INDEX, DONT WASTE TIME!
	CMP 	CL, 00H
	JE 		CHANGE_PLAYLIST_QUITING

	; Critical Section------------> Disable interrupt
	Audio_Change_Playlist_ENTER_CRITICAL_SECTION:
	cli

		; ES:SI   ---> SOURCE LOCATION 
		; DS:DI   ---> DESTINATION LOCATION
		MOV 	ES, AX
		MOV 	SI, BX

		MOV 	AX, SoundVariebles
		MOV 	DS, AX
		MOV 	DI, INDEX_BASE

		MOV 	BYTE PTR DS:STORED_INDEX 	, CL
		MOV 	BYTE PTR DS:INDEX_PTR   	, 0
		MOV 	BYTE PTR DS:PLAY_INDEX_LIST , TRUE

		; for(int i=0; i<cl; i++)  DESTINATION_BUFFER[i] = SOURCE[i];
		XOR 	BX, BX
		DEEP_COPY_FOR_LOOP_BEGINNING:
		CMP 	CL, BL
		JLE 	DEEP_COPY_FOR_LOOP_QUITING

			MOV 	AL 					, BYTE PTR ES:[SI+BX]
			MOV 	BYTE PTR DS:[DI+BX] 	, AL

		INC 	BL
		JMP 	DEEP_COPY_FOR_LOOP_BEGINNING
		DEEP_COPY_FOR_LOOP_QUITING:

	; Leaving Critical Section----> Enable interrupt
	Audio_Change_Playlist_QUIT_CRITICAL_SECTION:
	sti

	CHANGE_PLAYLIST_QUITING:
	POP 	DS
	POP 	ES
	POP 	SI
	POP 	DI
	POP		CX
	POP 	BX
	POP 	AX
	RET
Audio_Change_Playlist ENDP

;=====================================================================
;	FUNCTION NAME: ChangeNewSound
;	INPUTS: 		AL-> INDEX OF THE SOUND @ LookUpTable
;	OUTPUTS:		-
;	DESCRIPTION:	CHANGE PLAYING SOUND ACCORDING TO THE GIVEN INDEX
;					STORED IN AX REGISTER REGARDLESS OF WHETHER THE 
;					SYSTEM IS CURRENTLY PLAYYING SOUND OR NOT.
;	WHEN TO CALL:	WHEN THE SYSTEM WANT TO CHANGE/START THE SOUND.
;=====================================================================
ChangeNewSound PROC FAR
	PUSH	ES
	PUSH	AX
	PUSH	BX

	; CHANGE THE Current_ES, Current_BP, Current_Count WILL EVENTUALLY
	; CHANGE AND START THE NEW SOUND.
	; BECAUSE THE OutputNextLevel WILL CONSTANTLY CHECK THIS VARIABLES 
	; TO OUTPUT THE SOUND SIGNALS.
	MOV		BX, SoundVariebles
	MOV		ES, BX

	; (AX)=INDEX
	; (BX)=INDEX STEP IN LookUpTable = 6 BYTES PER INDEX
	; (AX)*(BX)=OFFSET OF INDEX IN LookUpTable
	MOV		BX, 0006H
	MUL		BL		; THE OFFSET STORED IN AX
	MOV		BX, AX	; THE OFFSET IS NOW STORED IN BX

	MOV		AX, WORD PTR ES:LOOKUP_BASE[BX]		; (AX)=ES OF THE SOUND
	MOV		WORD PTR ES:Current_ES, AX
	MOV		AX, WORD PTR ES:LOOKUP_BASE[BX+2]	; (AX)=BP OF THE SOUND
	MOV		WORD PTR ES:Current_BP, AX
	MOV		AX, WORD PTR ES:LOOKUP_BASE[BX+4]	; (AX)=COUNT FOR THAT SOUND
	MOV		WORD PTR ES:Current_Count, AX

	POP		BX
	POP 	AX
	POP		ES
	RET
ChangeNewSound ENDP

;=====================================================================
;	FUNCTION NAME: StopSound
;	INPUTS: 		-
;	OUTPUTS:		-
;	DESCRIPTION:	STOP THE AUDIO SYNTHESIZER TO PLAY ANY SOUND.
;	WHEN TO CALL:	WHEN THE SYSTEM WANT TO TURN OFF THE SOUND.
;=====================================================================
StopSound PROC FAR
	PUSH	ES
	PUSH	BX
	
	; CLEAR THE COUNTER WILL EVENTUALLY STOP THE SOUND
	; BECAUSE THE OUTPUTNEXTLEVEL FUNCTION USE COUNTER VALUE TO 
	; DETERMINE WHETHER TO SEND NEXT VOLTAGE VALUE
	MOV		BX, SoundVariebles
	MOV		ES, BX
	MOV		WORD PTR ES:Current_Count	, 00H
	MOV 	BYTE PTR ES:PLAY_INDEX_LIST	, FALSE
	
	POP		BX
	POP		ES
	RET
StopSound ENDP

;=====================================================================
;	FUNCTION NAME: OutputNextLevel
;	INPUTS: 		-
;	OUTPUTS:		-
;	DESCRIPTION:	THIS FUNCTION WILL OUTPUT THE APPRIOPIATE SOUND
;					SIGNALS 
;		***(USING THE Current_ES,Current_BP,Current_Count TO KEEP TRACK).
;		***(THE OUTPUT FREQUENCY IS THE RESPONSIBLE OF THE CALLER--->
;			---> THE CALLER NEED TO CALL THIS FUNCTION WITH APPRIOPRITE FREQUENCY.)
;	WHEN TO CALL:	THE SYSTEM NEED TO CALL THIS FUNCTION AT THE 
;					APPRIOPRIATE FREQUENCY ACCORDING TO THE SOUND DATA.
;=====================================================================
OutputNextLevel PROC FAR
	PUSH	ES
	PUSH	AX
	PUSH	BX
	PUSH	CX
	PUSH	DX

	MOV		BX, SoundVariebles
	MOV		ES, BX

	; IF THE COUNTER VALUE IS ZERO, NO ACTIONS WILL BE CARRIED OUT...
	MOV		CX,	WORD PTR ES:Current_Count
	CMP		CX, 0H
	JE		OutputNextLevel_QUITING

	MOV		BX, WORD PTR ES:Current_BP
	MOV		AX, WORD PTR ES:Current_ES

	; ALL NECCESSARY INFORMATION IS GATHERED AT THIS POINT
	; Current_Count, Current_BP, Current_ES ARE LOADED!
	; TIME TO READ THE SOUND SIGNALS AND SEND IT OUT!
	PUSH	ES
	MOV 	ES, AX
	XOR		AX, AX
	MOV		AL, BYTE PTR ES:[BX]
	MOV		DX, SOUND_SYNTHESIZER_IO_ADDRESS
	OUT 	DX, AL
	POP	ES

	; UPDATE INFORMATION BEFORE QUIT...
	DEC 	CX
	INC		BX	
	MOV		WORD PTR ES:Current_Count, CX
	MOV		WORD PTR ES:Current_BP,BX

	OutputNextLevel_QUITING:
	POP 	DX
	POP		CX
	POP		BX
	POP 	AX
	POP		ES
	RET
OutputNextLevel ENDP


;=====================================================================
;	FUNCTION NAME: FetchNextIndex
;	INPUTS: 		-
;	OUTPUTS:		-
;	DESCRIPTION:	THIS FUNCTION WILL FETCH THE NEXT INTEX IN THE 
;			 		INDEX LIST IF CURRENT INDEX IS FINISHED.
;
;	WHEN TO CALL:	NEED TO BE CALLED AT THE APPRIORIATE RATE TO ENSURE 
;					THE GAP BETWEEN INDEX IS NOT NOTICABLE.
;=====================================================================
FetchNextIndex PROC FAR
	PUSH 	AX
	PUSH 	BX
	PUSH 	ES

	MOV		BX, SoundVariebles
	MOV		ES, BX

	; CHECK IF CURRENTLY NOT PLAYING ANY SOUND, IF YES, QUIT...
	CMP 	WORD PTR ES:Current_Count, 0000H
	JNE 	FetchNextIndex_QUITING

	; CHECK IF LAST UPDATE IS A LIST UPDATE, IF NOT, QUIT...
	CMP 	BYTE PTR ES:PLAY_INDEX_LIST, FALSE
	JE 		FetchNextIndex_QUITING

	; THIS POINT ONWARD, CRITICAL SECTION
	FetchNextIndex_ENTER_CRITICAL_SECTION:
	CLI

		; CHECK IF THERE ARE MORE SOUND TO PLAY, IF NOT, QUIT...
		MOV 	BH, BYTE PTR ES:STORED_INDEX
		MOV 	BL, BYTE PTR ES:INDEX_PTR
		CMP 	BH, BL
		JLE 	FetchNextIndex_LEAVE_CRITICAL_SECTION

		; SHOULD FETCH NEXT INDEX TO PLAY THE SOUND BECAUSE IS VALID...
		XOR 	AX, AX
		XOR 	BH, BH
		MOV 	AL, BYTE PTR ES:INDEX_BUFFER[BX];
		CALL 	FAR PTR ChangeNewSound

		; UPDATE MEMORY AFTER CHANGE A NEW SOUND
		INC 	BL
		MOV 	BYTE PTR ES:INDEX_PTR, BL

	FetchNextIndex_LEAVE_CRITICAL_SECTION:
	STI

	FetchNextIndex_QUITING:
	POP 	ES
	POP 	BX
	POP 	AX
	RET
FetchNextIndex ENDP

Audio_Routine	ENDS

End
